﻿/*************************************
 * Creator:SW-PeiYang
 * DateTime:2023/2/14 10:22:07
 * Description:<Description>
 * CopyRight:中广核研究院有限公司
 * ***********************************/

using Cpjit.SunnyPaine.Toolkit.Net.Sockets.IDataHandlingApdapter;
using Cpjit.SunnyPaine.Toolkit.Net.Sockets.Model;
using Cpjit.SunnyPaine.Toolkit.Net.Sockets.Util;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace Cpjit.SunnyPaine.Toolkit.Net.Sockets.TCP.Impl
{
    /// <summary>
    /// 提供一个TCP服务端。
    /// </summary>
    public class TcpServer : ITcpServer
    {
        #region 私有变量
        private bool isRunning = false;
        /// <summary>
        /// 最大客户端连接数。
        /// </summary>
        private int maxClient;
        /// <summary>
        /// tcp服务端对象。
        /// </summary>
        private Socket tcpServer;
        /// <summary>
        /// 接收数据的缓存大小。
        /// </summary>
        private int bufferSize = ushort.MaxValue;
        private Encoding encoder = Encoding.UTF8;
        #endregion


        #region 公共属性
        /// <summary>
        /// TCP客户端。
        /// </summary>
        public ConcurrentDictionary<string, SocketSession> TcpClients { get; }
        /// <summary>
        /// 监听的IP地址。
        /// </summary>
        public IPAddress Host { get; }
        /// <summary>
        /// 监听的端口。
        /// </summary>
        public int Port { get; }
        /// <summary>
        /// 是否使用客户端心跳监测。
        /// </summary>
        public bool IsUseHeartbeat { get; set; }
        /// <summary>
        /// 心跳保活周期。单位：ms。
        /// </summary>
        public int KeepAliveInterval { get; set; }
        /// <summary>
        /// 是否启用自动应答。
        /// </summary>
        public bool IsAutoReplay { get; set; }
        /// <summary>
        /// 自动应答内容。默认为 SUCCESS。
        /// </summary>
        public string ReplayContent { get; set; } = "SUCCESS";
        /// <summary>
        /// 字符编码。
        /// </summary>
        public Encoding Encoder
        {
            get { return this.encoder; }
            set
            {
                this.encoder = value;
                ByteBlock.Encoder = value;
            }
        }
        /// <summary>
        /// 数据处理适配器。
        /// </summary>
        public IDataHandlingAdapter Adapter { get; private set; }
        #endregion


        #region 事件委托
        /// <summary>
        /// 当收到消息时发生。
        /// </summary>
        public event Action<ITcpServer, SessionEventArgs> OnReceived;
        /// <summary>
        /// 当有客户端连接时发生。
        /// </summary>
        public event Action<ITcpServer, SessionEventArgs> OnConnected;
        /// <summary>
        /// 当有客户端断开连接时发生。
        /// </summary>
        public event Action<ITcpServer, SessionEventArgs> OnDisConnected;
        /// <summary>
        /// 当出现错误时发生。
        /// </summary>
        public event Action<ITcpServer, SessionEventArgs, Exception> OnError;
        #endregion


        #region 构造方法
        /// <summary>
        /// 使用指定的IP和端口创建<see cref="TcpServer"/>的实例。
        /// </summary>
        /// <param name="port">监听的端口。</param>
        public TcpServer(int port) : this(null, port, 1024)
        { }

        /// <summary>
        /// 使用指定的IP和端口创建<see cref="TcpServer"/>的实例。
        /// </summary>
        /// <param name="ip">监听的IP。</param>
        /// <param name="port">监听的端口。</param>
        public TcpServer(string ip, int port) : this(ip, port, 1024)
        { }

        /// <summary>
        /// 使用指定的IP和端口创建<see cref="TcpServer"/>的实例。
        /// </summary>
        /// <param name="ip">监听的IP。</param>
        /// <param name="port">监听的端口。</param>
        /// <param name="maxClient">最大客户端连接数。</param>
        public TcpServer(string ip, int port, int maxClient)
        {
            IPAddress host;
            if (string.IsNullOrEmpty(ip))
            {
                host = IPAddress.Any;
            }
            else if (!IPAddress.TryParse(ip, out host))
            {
                throw new ArgumentException("IP地址不合法。");
            }

            this.Host = host;
            this.Port = port;
            this.maxClient = maxClient;

            this.TcpClients = new ConcurrentDictionary<string, SocketSession>();
        }
        #endregion


        #region 私有方法
        /// <summary>
        /// 异步接收客户端连接的回调方法。
        /// </summary>
        /// <param name="iar"></param>
        private void AcceptClientCallback(IAsyncResult iar)
        {
            if (!this.isRunning)
            {
                return;
            }

            try
            {
                Socket client = this.tcpServer.EndAccept(iar);
                SocketSession session = new SocketSession(client, this.bufferSize);
                EndPoint endPoint = new IPEndPoint(IPAddress.Parse(session.ClientIP), session.ClientPort);

                if (this.TcpClients.Count >= this.maxClient)
                {
                    this.OnError?.Invoke(this, new SessionEventArgs(),
                        new IndexOutOfRangeException("服务端超出最大客户端连接数。"));
                }
                else
                {
                    if (this.TcpClients.ContainsKey(session.IPAddress) == false)
                    {
                        this.TcpClients.TryAdd(session.IPAddress, session);
                        this.OnConnected?.Invoke(this, new SessionEventArgs()
                        {
                            RemoteEndPoint = endPoint,
                        });
                    }

                    //开始接收来自客户端的数据
                    client.BeginReceive(session.Buffer, 0, this.bufferSize, SocketFlags.None,
                        new AsyncCallback(this.ReceiveDataCallback), session);
                }

                //继续接收下一个客户端请求
                this.tcpServer.BeginAccept(new AsyncCallback(this.AcceptClientCallback), this.tcpServer);
            }
            catch (Exception ex)
            {
                //继续接收下一个客户端请求
                this.tcpServer.BeginAccept(new AsyncCallback(this.AcceptClientCallback), this.tcpServer);
                this.OnError?.Invoke(this, new SessionEventArgs(), ex);
            }
        }

        /// <summary>
        /// 异步接收消息的回调方法。
        /// </summary>
        /// <param name="iar"></param>
        private void ReceiveDataCallback(IAsyncResult iar)
        {
            if (!this.isRunning)
            {
                return;
            }

            SocketSession session = iar.AsyncState as SocketSession;
            Socket client = session.Client;
            EndPoint endPoint = new IPEndPoint(IPAddress.Parse(session.ClientIP), session.ClientPort);
            if (this.TcpClients.ContainsKey(session.IPAddress))
            {
                if (this.TcpClients[session.IPAddress].LastReceiveTime < DateTime.Now)
                {
                    this.TcpClients[session.IPAddress].LastReceiveTime = DateTime.Now;
                }
            }

            try
            {
                int receiveCount = client.EndReceive(iar);
                if (receiveCount == 0)//表示客户端已经断开连接
                {
                    this.CloseSession(session);
                    this.OnDisConnected?.Invoke(this, new SessionEventArgs()
                    {
                        RemoteEndPoint = endPoint
                    });
                }
                else if (receiveCount > 0)//表示客户端连接正常
                {
                    session.ReceivedCount = receiveCount;
                    if (this.Adapter != null)
                    {
                        this.Adapter.PreviewReceived(session);
                    }
                    else
                    {
                        byte[] data = new byte[receiveCount];
                        Array.Copy(session.Buffer, 0, data, 0, receiveCount);
                        this.OnReceived?.Invoke(this, new SessionEventArgs()
                        {
                            RemoteEndPoint = endPoint,
                            Data = data
                        });
                    }

                    //继续接收来自客户端的消息
                    client.BeginReceive(session.Buffer, 0, this.bufferSize, SocketFlags.None,
                        new AsyncCallback(this.ReceiveDataCallback), session);
                }
            }
            catch (SocketException ex)
            {
                if (ex.SocketErrorCode != SocketError.Success)
                {
                    try { this.CloseSession(session); }
                    catch { }
                    this.OnDisConnected?.Invoke(this, new SessionEventArgs()
                    {
                        RemoteEndPoint = endPoint
                    });
                    SocketSession tmp;
                    this.TcpClients.TryRemove(endPoint.ToString(), out tmp);
                }
            }
            catch (Exception ex)
            {
                this.OnError?.Invoke(this, new SessionEventArgs()
                {
                    RemoteEndPoint = endPoint,
                }, new Exception($"处理从客户端{session.IPAddress}接收的消息出错。", ex));
                //继续接收来自客户端的消息
                client.BeginReceive(session.Buffer, 0, this.bufferSize, SocketFlags.None,
                    new AsyncCallback(this.ReceiveDataCallback), session);
            }
        }

        /// <summary>
        /// 发送消息的回调方法。
        /// </summary>
        /// <param name="iar"></param>
        private void SendCallback(IAsyncResult iar)
        {
            if (iar.IsCompleted)
            {
                Socket socket = iar.AsyncState as Socket;
                if (socket.Connected)
                {
                    socket.EndSendTo(iar);
                }
            }
        }

        /// <summary>
        /// 关闭客户端会话。
        /// </summary>
        /// <param name="session"></param>
        private void CloseSession(SocketSession session)
        {
            if (session != null)
            {
                session.Close();
                SocketSession tmp;
                this.TcpClients.TryRemove(session.IPAddress, out tmp);
            }
        }

        /// <summary>
        /// 心跳响应。
        /// </summary>
        private void HeartbeatPong()
        {
            Task.Factory.StartNew(() =>
            {
                while (this.isRunning)
                {
                    string ipport = null;
                    foreach (KeyValuePair<string, SocketSession> item in this.TcpClients)
                    {
                        if (Math.Abs((DateTime.Now - item.Value.LastReceiveTime).TotalMilliseconds) > 5000)
                        {
                            ipport = item.Key;
                            break;
                        }
                    }
                    if (!string.IsNullOrEmpty(ipport))
                    {
                        this.TcpClients[ipport].Client.Close();
                        SocketSession tmp;
                        this.TcpClients.TryRemove(ipport, out tmp);
                    }
                }
            });
        }

        /// <summary>
        /// 析构函数。
        /// </summary>
        ~TcpServer()
        {
            this.Stop();
        }
        #endregion


        #region 公共方法
        /// <summary>
        /// 设置处理器适配器。
        /// </summary>
        /// <param name="adapter"></param>
        public void SetDataHandlingAdapter(IDataHandlingAdapter adapter)
        {
            if (this.Adapter == null)
            {
                throw new ArgumentNullException("adapter");
            }
            this.Adapter = adapter;
            this.bufferSize = this.Adapter.Capacity + 4;//4表示指示有效数据长度的包头
            this.Adapter.GoReceived += (session, data) =>
            {
                this.OnReceived?.Invoke(this, new SessionEventArgs()
                {
                    RemoteEndPoint = new IPEndPoint(IPAddress.Parse(session.ClientIP), session.ClientPort),
                    Data = data
                });
            };
            this.Adapter.GoSend += (client, data, isAsync) =>
            {
                if (isAsync)
                {
                    client.BeginSend(data, 0, data.Length, SocketFlags.None,
                        new AsyncCallback(this.SendCallback), client);
                }
                else
                {
                    client.Send(data, 0, data.Length, SocketFlags.None);
                }
            };
        }
        #endregion

        #region 公共方法，ITcpServer成员
        /// <summary>
        /// 开始监听。
        /// </summary>
        /// <returns></returns>
        public bool Start()
        {
            if (this.isRunning)
            {
                throw new Exception("服务已启动监听。");
            }

            try
            {
                this.tcpServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                this.tcpServer.Bind(new IPEndPoint(this.Host, this.Port));
                this.tcpServer.Listen(this.maxClient);
                this.tcpServer.BeginAccept(new AsyncCallback(this.AcceptClientCallback), this.tcpServer);
                if (this.IsUseHeartbeat)
                {
                    this.HeartbeatPong();
                }
                this.isRunning = true;
                return true;
            }
            catch (Exception ex)
            {
                this.OnError?.Invoke(this, null, ex);
                return false;
            }
        }

        /// <summary>
        /// 停止监听并释放连接。作用同<see cref="Dispose"/>。
        /// </summary>
        /// <returns></returns>
        public void Stop()
        {
            this.isRunning = false;
            this.Adapter?.Dispose();
            this.tcpServer.Dispose();
        }

        /// <summary>
        /// 停止监听并释放连接。作用同<see cref="Stop"/>。
        /// </summary>
        public void Dispose()
        {
            this.Stop();
        }

        /// <summary>
        /// 发送消息。
        /// </summary>
        /// <param name="message"></param>
        public void Send(string message)
        {
            byte[] datas = this.Encoder.GetBytes(message);
            this.Send(datas);
        }

        /// <summary>
        /// 发送消息。
        /// </summary>
        /// <param name="clientId">客户端Id。</param>
        /// <param name="message"></param>
        public void Send(string clientId, string message)
        {
            byte[] datas = this.Encoder.GetBytes(message);
            this.Send(clientId, datas);
        }

        /// <summary>
        /// 发送消息。
        /// </summary>
        /// <param name="endPoint">客户端终结点。</param>
        /// <param name="message"></param>
        public void Send(EndPoint endPoint, string message)
        {
            byte[] datas = this.Encoder.GetBytes(message);
            this.Send(endPoint, datas);
        }

        /// <summary>
        /// 发送消息。
        /// </summary>
        /// <param name="datas"></param>
        public void Send(byte[] datas)
        {
            foreach (SocketSession item in this.TcpClients.Values)
            {
                this.Adapter.Send(item.Client, datas, false, null);
            }
        }

        /// <summary>
        /// 发送消息。
        /// </summary>
        /// <param name="clientId">客户端Id。</param>
        /// <param name="datas"></param>
        public void Send(string clientId, byte[] datas)
        {
            if (this.TcpClients.ContainsKey(clientId))
            {
                this.Adapter.Send(this.TcpClients[clientId].Client, datas, false, null);
            }
        }

        /// <summary>
        /// 发送消息。
        /// </summary>
        /// <param name="endPoint">客户端终结点。</param>
        /// <param name="datas"></param>
        public void Send(EndPoint endPoint, byte[] datas)
        {
            string ipport = endPoint.ToString();
            Socket client = null;
            if (!this.TcpClients.ContainsKey(ipport))
            {
                foreach (SocketSession item in this.TcpClients.Values)
                {
                    if (string.Equals(item.IPAddress, ipport))
                    {
                        client = item.Client;
                    }
                }
            }
            if (client != null)
            {
                this.Adapter.Send(client, datas, false, null);
            }
        }

        /// <summary>
        /// 异步发送消息。
        /// </summary>
        /// <param name="message"></param>
        public void SendAsync(string message)
        {
            byte[] datas = this.Encoder.GetBytes(message);
            this.SendAsync(datas);
        }

        /// <summary>
        /// 异步发送消息。
        /// </summary>
        /// <param name="clientId">客户端Id。</param>
        /// <param name="message"></param>
        public void SendAsync(string clientId, string message)
        {
            byte[] datas = this.Encoder.GetBytes(message);
            this.SendAsync(clientId, datas);
        }

        /// <summary>
        /// 异步发送消息。
        /// </summary>
        /// <param name="endPoint">客户端终结点。</param>
        /// <param name="message"></param>
        public void SendAsync(EndPoint endPoint, string message)
        {
            byte[] datas = this.Encoder.GetBytes(message);
            this.SendAsync(endPoint, datas);
        }

        /// <summary>
        /// 异步发送消息。
        /// </summary>
        /// <param name="datas"></param>
        public void SendAsync(byte[] datas)
        {
            foreach (SocketSession item in this.TcpClients.Values)
            {
                this.Adapter.Send(item.Client, datas, true, this.SendCallback);
            }
        }

        /// <summary>
        /// 异步发送消息。
        /// </summary>
        /// <param name="clientId">客户端Id。</param>
        /// <param name="datas"></param>
        public void SendAsync(string clientId, byte[] datas)
        {
            if (this.TcpClients.ContainsKey(clientId))
            {
                this.Adapter.Send(this.TcpClients[clientId].Client, datas, true, this.SendCallback);
            }
        }

        /// <summary>
        /// 异步发送消息。
        /// </summary>
        /// <param name="endPoint">客户端终结点。</param>
        /// <param name="datas"></param>
        public void SendAsync(EndPoint endPoint, byte[] datas)
        {
            string ipport = endPoint.ToString();
            Socket client = null;
            if (!this.TcpClients.ContainsKey(ipport))
            {
                foreach (SocketSession item in this.TcpClients.Values)
                {
                    if (string.Equals(item.IPAddress, ipport))
                    {
                        client = item.Client;
                    }
                }
            }
            if (client != null)
            {
                this.Adapter.Send(client, datas, true, this.SendCallback);
            }
        }
        #endregion
    }
}
